require "./common.rb"

# usage of placeholder, no need concatenating with "+"
def sayGoodbye(name)
  puts "Goodbye, #{name}" 
end
sayGoodbye("Jeff")
puts

# Hashes
employee = {
  'jeff' => 'techy',
  'abdul' => 'lead',
  'weeteck' => 'PM'
}
puts employee['jeff']

balls = Hash.new
puts balls['soccer']
balls['soccer'] = "Football"
puts balls['soccer']

drinks = Hash.new('coke')
puts drinks['drink1']
puts

# code block
# yield(puts "Yielding sth") # "yield" should not be called outside of a block
# "yield" is similar to "invoke" in Java reflection
def callBlock
  yield
  yield
end
callBlock {puts "In the Block"}

def callBlock2
  yield 'param_me'
end
callBlock2 {| param | puts "param is " + param}
# in fact, the .each{} method of array is exactly implemented in the same way
puts

# line = gets # get user input, typical blocking operation
# puts line

# class & object
class Account
  def initialize(name, age)
    @name = name
    @age = age
  end

  def descInline
    "This guy: #{@name} , #{@age}"
  end
end
account1 = Account.new("Jeff", 30)
puts account1.inspect # "inspect" being used for monitor state of object
puts account1.descInline

class SvnAccount < Account
  def initialize(name, age, ip)
    super(name, age)
    @ip = ip
  end

  def descInline
    super + ", #{@ip}"
  end
end
account2 = SvnAccount.new("Jeff", 30, "192.168.101.66")
puts account2.descInline
puts

# here style below auto create all the instance vars with their getter/setter
class Song
  attr_accessor :name, :lyrics, :author
end
song1 = Song.new
song1.lyrics = "love ever"

# "static" variable & method like in JAVA
class Person
  def Person.type
    @@type = "Human"
  end
end
puts Person.type

# Singleton pattern
class Logger
  private_class_method(:new) # so user cannot create instance by Logger.new
  @@logger = nil
  def Logger.create
    @@logger = new unless @@logger
    @@logger
  end
end
puts Logger.create.inspect
puts Logger.create.inspect 

# accessibility
class ThingA
  private
  def methodPrivate
    puts "method private A"
  end
  protected
  def methodProtected
    puts "method protected A"
  end
  public
  def methodPublic
    puts "method public A"
    methodPrivate
  end
end
class ThingB
  def methodPrivate
    puts "method private B"
  end
  def methodProtected
    puts "method protected B"
  end
  def methodPublic
    puts "method public B"
    methodPrivate
  end
  
  private  :methodPrivate
  protected :methodProtected
  public :methodPublic
end
thingA = ThingA.new
thingB = ThingB.new
thingA.methodPublic
thingB.methodPublic

# referencing variable
person1 = "Tim"
person2 = person1
person2[0] = "J"
puts person1 # person1 becomes "Jim"
person1 = "Tim"
person2 = person1.dup # also look at "clone"
person2[0] = "J"
puts person1 + " , " + person2

# array
a = [1,3,5,7,9]
puts "#{a[-1]} , #{a[-2]}"
printArray(a[-3,2])
a[10] = 13
printArray(a)
# ruby array is so flexible, basically it must be implemented in hash way
puts

# implementing an ArrayList
class ThingC
  def initialize(id,name)
    @id = id
    @name = name
  end

  attr_reader :name, :id
  
  def to_s
    "c_#{@id}"
  end
end

class ThingCList
  def initialize
    @list = Array.new
  end

  def append(obj)
    @list.push(obj)
    self # very useful of returning "self", so that you can "chain" the operation
  end

  def deleteFirst
    @list.shift
  end

  def deleteLast
    @list.pop
  end

  def [](key)
#    if key.kind_of?(Integer) # if use array index to access
#      @list[key]
#    else
#      @list.find { |item| item.name == key}
#    end
    # below is a more succinct version of above code
    return @list[key] if key.kind_of?(Integer)
    return @list.find { |item| item.name == key} 
  end

  def to_s
    result = ""
    @list.each { |item| result += item.to_s + "," }
    result
  end
end

list = ThingCList.new
list.append(ThingC.new(1, "thingc_1")).
  append(ThingC.new(2, "thingc_2")).
  append(ThingC.new(3, "thingc_3")).
  append(ThingC.new(4, "thingc_4"))
puts list.to_s
puts list[2]
puts list["thingc_3"]


# block
# What makes blocks interesting, however, is that you can PASS parameters
# to them and RECEIVE values back from them

# print a Fibonacci series
def fibUpTo(max)
  i1, i2 = 1, 1
  while i1 <= max
    # call printing block & back to the function
    result = yield i1
    print result
    i1, i2 = i2, i1 + i2
  end
end
# block can return value to function
fibUpTo(1000) {| fibNum | fibNum.to_s(10) + " " }
puts

# iterator in I/O
file = File.open(getFileName) do | file |
  puts file.readline
end
# do/end block works same as {} block
puts


class File 
  # and, block can actually be used to implement AOP
  def File.openAndProcess(*args)
    f = File.open(*args)
    puts "AOP @Before"
    yield f
    puts "AOP @After"
    f.close
  end
  
  # to be more flexible, make a single method to handle with/without block cases
  def File.myOpen(*args)
    f = File.open(*args) 
    if block_given? # check wether there's block given in current context
      yield f
      f.close
      f = nil
    end
    f
  end
end
# "define" class File in here is not defining a new class File independent from 
# the ruby IO type
File.openAndProcess(getFileName) {| f | puts f.readline}
f = File.myOpen(getFileName) #{|f| puts "file closed"}
puts f.readline unless (f == nil)
puts

# closure magic
class JukeboxButton
  def initialize(label, &action)
    @label = label
    @action = action
  end

  attr_reader :label

  def pressed
    @action.call(self)
  end
end
# consider: one may want to write logic code into the callback method "pressed()",
# but it is wrong because the button shouldn't know what will happen when it is
# pressed until it got installed in a Jukebox machine. Here, instead, invoke the
# block which referenced by passing action with "&".
button = JukeboxButton.new("Play Button") { |btn| puts "U pressed " + btn.label }
button.pressed
puts


# string, other ways of expressing.
str = "I am Jeff" # the way we use as usual
str = %q/I am Jeff/ # use %q as '' string
str = %Q{I am Jeff} # use %Q as " " string
puts str
# place-holder
str = "Now time is #{60*60*24} per day"
puts str
puts


# string process practise: song list parsing from file
class Song
  def initialize(category, name, author, id)
    @category = category
    @name = name
    @author = author
    @id = id
  end

  attr_reader :category, :name, :author, :id

  def to_s
    "#{@category},#{@name},#{@author},#{@id}"
  end
end

class WordIndex
  def initialize
    @index = Hash.new
  end

  def index(obj, *phrases)
    phrases.each { | aPhrase |
      aPhrase.scan(/\w[-\w']+/) { | aWord |
        aWord.downcase!
        @index[aWord] = [] if @index[aWord].nil?
        @index[aWord].push(obj)
      }
    }
  end

  def lookup(aWord)
    @index[aWord.downcase!]
  end
end

class SongList
  def initialize
    @list = Array.new
    @wordIndex = WordIndex.new
  end

  def append(obj)
    @wordIndex.index(obj, obj.name, obj.author)
    @list.push(obj)
    self # very useful of returning "self", so that you can "chain" the operation
  end

  def [](key)
    return @list[key] if key.kind_of?(Integer)
    return @list.find { |item| item.name == key}
  end

  def lookup(aWord)
    @wordIndex.lookup(aWord)
  end

  def to_s
    result = ""
    @list.each { |item| result += item.to_s + "," }
    result
  end
end
songList = SongList.new

File.open(getFileName) { |file|
  file.each { |line|
    category, name, author, id = line.chomp.split(/\s*\|\s*/)
    # seems in 1.8.7 there's no need to squeeze! which may automatically done
    songList.append(Song.new(category, name, author, id))
  }
}
puts songList.lookup("Morton")


# range for Object
class VU
  def initialize(volumn)
    @volumn = volumn
  end

  attr_reader :volumn

  def to_s
    '#' * @volumn
  end

  include Comparable

  def <=>(another)
    @volumn <=> another.volumn
  end

  def succ
    # @volumn.between?(0, 9) can be expressed as (0..9 === @volumn)
    raise(IndexError, "Volumn out of range") unless @volumn.between?(0, 9)
    VU.new(@volumn.succ)
  end
end

medium = VU.new(4)..VU.new(7)
puts medium.to_a
puts medium.include?(VU.new(3))
puts


# RegEx, =~ is the match operator
def showRE(a, re)
  if a =~ re
    "#{$`}<<#{$&}>>#{$'}" # shortcut of MatchData parts, which is kind of ThreadLocal variable
  else
    "no match"
  end
end
puts showRE("interesting", /t/)
re = /\|/ # matching "|", since | itself has meaning in regex syntax, must be escaped to be matched

# O-O RegEx, MatchData investigate
re = /(\d+):(\d+)/
md = re.match("Time: 6:32pm")
puts md[0] # == $&
puts md[1] # == $1
puts md[2] # == $2
puts md.pre_match # == $`
puts md.post_match # == $'
puts md # == $~
puts


# function
def defaultValsFunc(arg1="jeff1", arg2="jeff2", arg3="jeff3")
  puts "#{arg1}-#{arg2}-#{arg3}"
end
defaultValsFunc # call without passing any param

def anyNumParamFunc(arg1, *rest)
  puts "#{arg1}--#{rest.join("+")}"
end
anyNumParamFunc("Jeff", "goal1", "goal2", "goal3")

def five(a,b,c,d,e)
  puts "I've passed #{a} #{b} #{c} #{d} #{e}"
end
# explode the array before passing into function with each element as a separate param
five(1,2,3, *['c','d'])
five(*10..14)

# make blocks more flexible. practice: math table
# version 1: works but ugly
num = 2
type = "plus"
if type =~ /^t/
  puts ((1..10).collect { |n| n * num }.join(","))
else
  puts ((1..10).collect { |n| n + num }.join(","))
end
# version 2:
# If the last argument to a method is preceded by an ampersand,
# Ruby assumes that it is a Proc object. It removes it from the parameter list,
# converts the Proc object into a block, and associates it with the method.
if type=~ /^t/
  calc = proc { |n| n * num }
else
  calc = proc { |n| n + num }
end
puts (1..10).collect(&calc).join(",")
# since block also can return value to the caller, it becomes the source of many miracles...
puts

# collected hashed arguments
def hashArgFunc(id, param)
  puts "#{id}: #{param['name']}-#{param['age']}"
end
hashArgFunc(3, 'name'=>"jeff", 'age'=> 30)
# don't have plain named argument passing like in scala
puts

# typical operator expression application: operator overriding
class MyNum
  def initialize(val)
    return @val = val unless val.nil?
    return @val = 0
  end

  attr_reader :val

  def +(other)
    MyNum.new(@val + other.val)
  end

  def to_s
    "#{@val}"
  end
end
puts MyNum.new(3) + MyNum.new(4) # just as simply as in Scala

# %x enclose a executable shell script block
puts %x{echo %JAVA_HOME%}

# parallel assignment
x = 1
a, b, c = x, x += 1, x += 1
puts "#{a}, #{b}, #{c}"
a = [1,2,3,4]
b, c = a
puts "#{b}, #{c}"
b, *c = a
puts "#{b}, #{c}"
b, (c,*d), e = 1,[2,3,4],5
puts "#{b}, #{c}, #{d}, #{e}"
puts


# boolean expression
def testDefined
  puts defined? yield
  puts defined? String
  puts defined? $_
  puts defined? gumgum  #nil
  puts defined? Math::PI
  puts defined? print
  puts defined? c,d = 1,2 #assignment
end
testDefined {}
puts

str1 = "String 1"
str2 = "String 1"
puts "#{str1.eql?(str2)} -- #{str1.equal?(str2)}" #true -- false

# negated "if"
unless 1 < 2
  puts "I am idiot"
else
  puts "I am genius"
end

# case when [test] then [result]...This test is done using comparison === target
num = 10
result = case num
when 1..10 then "Small"
when 11..20 then "Medium"
when 21..30 then "Large"
end
puts result
puts 

# .each loop, extended .each is .grep, which means .each+filtered
File.open(getFileName).grep(/Jeff/) { |line| 
  puts line 
}

# for..in as syntatic sugar
class MusicType
  def each
    yield "Classic"
    yield "Pop"
    yield "Jazz"
  end
end
for type in MusicType.new
  print "#{type}, "
end
puts


# error/exception handling
opName = "c:/temp/rubyTemp.txt"
opFile = File.new(opName, "w");
retried = false
begin # "try" in Java
  unless retried
    while data = socket.read(512)
      opFile.write(data)
    end
  else
    puts "retried already, no use"
  end
  
rescue StandardError => err # "catch" in Java
  $stderr.print "IO failed: #{$!} \n"
  $stderr.print "IO failed: #{err} \n" # exactly same as the above line
  if retried
    opFile.close
    File.delete(opName)
  else
    retried = true
    retry # restart from "begin" and try again
  end
  
  raise 
ensure  # "finally" in Java
  opFile.close unless opFile.closed?
end

# catch & throw, not as primitive as in JAVA
#catch :gotcha do
#  puts "error caught!"
#end
#def testGotcha
#  throw :gotcha
#end
#testGotcha

# Module
require "./m1.rb"
puts "#{M1::C1} -- #{M1.calc(3,4)}"

# mixin makes module a powerful superclass
require "./debug.rb"
class Pen
  def initialize(brand)
    @brand = brand
  end
  attr_reader :brand 
  
  include Debug # mixin module Debug
end
aPen = Pen.new("China")
puts aPen.whoAmI?

# you define the one method, <=>, include Comparable, and get six comparison functions for free:
# (<, <=, ==, >=, and >)
class ThingD
  def initialize(seq)
    @seq = seq
  end
  
  attr_reader :seq 
  
  include Comparable
  def <=>(other)
    self.seq <=> other.seq
  end
end
d1 = ThingD.new(1)
d2 = ThingD.new(2)
d3 = ThingD.new(3)
d4 = ThingD.new(3)
puts d1 < d2
puts d3 < d2
puts "#{d3 == d4} -- #{d3.equal?(d4)}"

# when mixin codes interact with mixin class
class Array
  include Inject # defined in ./common.rb
end
puts [1,2,3,4,5].sum
puts [1,2,3,4,5].product1
puts
# consider: try avoid giving a module an object state, or it will be collide with
# other state(with the same name) during mixined.

# create a customed collection class with many neat-o methods, mixin Enumerable & implement "each {block}" is the most easy way.

# require/load as executable statement
if false
  require "./non-exists.rb"
end

